use crate::chunker::StatelessChunker;
use std::io::{Read, Result as IoResult};

/// Chunking data from an [Read].
///
/// `M` is the buffer size, this implmentations requires `M` being
/// equal to the maximum chunk size.
pub struct SyncChunk<const M: usize, R: Read, C: StatelessChunker<M>> {
    src: R,
    chunker: C,
    buf: Box<[u8; M]>,

    input_ended: bool,
    filled: usize,
    last_chunk_len: usize,
}

impl<const M: usize, R: Read, C: StatelessChunker<M>> SyncChunk<M, R, C> {
    pub fn new(src: R, chunker: C) -> Self {
        Self {
            src,
            chunker,
            buf: Box::new([0u8; M]),
            filled: 0,
            input_ended: false,
            last_chunk_len: 0,
        }
    }

    pub fn chunk_next_ref_mut(&mut self) -> IoResult<Option<(&'_ mut [u8], C::Output)>> {
        if self.last_chunk_len > 0 {
            self.buf.copy_within(self.last_chunk_len..self.filled, 0);
        }
        let already_filled = self.filled - self.last_chunk_len;
        self.filled = if self.input_ended {
            already_filled
        } else {
            let mut cur = already_filled;
            loop {
                let n = self.src.read(&mut self.buf[cur..])?;
                if n == 0 {
                    break;
                }
                cur += n;
            }
            cur
        };

        if self.filled == 0 {
            self.last_chunk_len = 0;
            Ok(None)
        } else if self.filled < M {
            self.input_ended = true;
            match self.chunker.try_chunk(&self.buf[..self.filled]) {
                Ok((l, out)) => {
                    self.last_chunk_len = l;
                    Ok(Some((&mut self.buf[..l], out)))
                }
                Err(out) => {
                    self.last_chunk_len = self.filled;
                    Ok(Some((&mut self.buf[..self.filled], out)))
                }
            }
        } else {
            let (l, out) = self.chunker.chunk(&*self.buf);
            self.last_chunk_len = l;
            Ok(Some((&mut self.buf[..l], out)))
        }
    }

    /* pub fn chunk_next(&mut self) -> IoResult<Option<(usize, C::Output)>> {
        let try_filled = if self.input_ended {
            self.filled
        } else {
            self.src.read(&mut self.buf[self.filled..])? + self.filled
        };

        if try_filled == 0 {
            Ok(None)
        } else if try_filled < M {
            self.input_ended = true;
            match self.chunker.try_chunk(&self.buf[..try_filled]) {
                Ok((l, out)) => {
                    self.buf.copy_within(l.., 0);
                    self.filled = try_filled - l;
                    Ok(Some((l, out)))
                }
                Err(out) => {
                    self.filled = 0;
                    Ok(Some((try_filled, out)))
                }
            }
        } else {
            let (l, out) = self.chunker.chunk(&*self.buf);
            self.buf.copy_within(l.., 0);
            self.filled = try_filled - l;
            Ok(Some((l, out)))
        }
    } */
}
